/* 
 * Copyright (c) 2012, Fromentin Xavier, Schnell Michaël, Dervin Cyrielle, Brabant Quentin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *      * The names of its contributors may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Fromentin Xavier, Schnell Michaël, Dervin Cyrielle OR Brabant Quentin 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package kameleon.gui.view ;

import java.awt.Cursor;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.TitledBorder;

import kameleon.exception.FileReadingException;
import kameleon.exception.KameleonException;
import kameleon.gui.exception.UnknownKeyException;
import kameleon.gui.language.SwitchLanguage;
import kameleon.gui.model.FileModel;
import kameleon.gui.model.GenerationMessage;
import kameleon.gui.model.GenerationMessage.State;
import kameleon.gui.model.Message;
import kameleon.gui.model.Model;
import kameleon.gui.util.FileConstants;
import kameleon.gui.util.ImageUtility;
import kameleon.gui.util.LanguageConstants;

/**
 * View displaying the progress of a file generation process. Displays the names
 * of the source files and the target files. Also offers the possibility to re-run
 * the generation process.
 * 
 * @author		Schnell Michaël
 * @version		1.0
 */
class ViewGenerationMessage extends ViewMessage 
implements FileConstants, LanguageConstants {

	/**
	 * Needed to serialize this class.
	 * 
	 * @see		java.io.Serializable
	 */
	private static final long serialVersionUID = -1477193510863283257L ;
	
	/**
	 * Font used by file paths.
	 */
	private final Font TRUETYPE = new Font(
			this.getFont().getName(), Font.TRUETYPE_FONT, this.getFont().getSize()) ;

	/**
	 * Label displaying the title for the source file.
	 */
	private JLabel convertingTitle ;

	/**
	 * Labels displaying the title for every generated file.
	 */
	private JLabel[] toTitles ;

	/**
	 * Label displaying the status of the generation process.
	 */
	private JLabel messageLabel ;

	/**
	 * Button used to trigger the re-run of the generation process.
	 */
	private JButton fastGeneration ;

	/**
	 * Layout manager used by this component.
	 */
	private GridBagLayout gridbag ;

	/**
	 * Icons displaying the progress for every generated file.
	 */
	private JLabel[] progressIcons ;

	/**
	 * Sole constructor.
	 * 
	 * @param 	model
	 * 			model used by this view
	 * 
	 * @param 	message
	 * 			model backing the information about the displayed message
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 *
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	public ViewGenerationMessage(Model model, final GenerationMessage message) 
			throws FileReadingException, UnknownKeyException	{
		this(new GridBagLayout(), model, message) ;
	}// ViewGenerationMessage(Model, GenerationMessage)

	/**
	 * Private constructor.
	 * 
	 * @param	gridbag
	 * 			layout manager used by this instance
	 * 
	 * @param 	model
	 * 			model used by this view
	 * 
	 * @param 	message
	 * 			model backing the information about the displayed message
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 * 
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	private ViewGenerationMessage(GridBagLayout gridbag, Model model, 
			final GenerationMessage message) 
					throws FileReadingException, UnknownKeyException {
		super(model, message) ;
		this.gridbag = gridbag ;
		this.setLayout(this.gridbag) ;
		this.setBorder(BorderFactory.createTitledBorder(
				FileModel.DATE_FORMATTER.format(message.getLastGeneration()))) ;

		this.build(message) ;
	}// ViewGenerationMessage(GridBagLayout, Model, GenerationMessage)

	/**
	 * Builds the content of this instance.
	 * 
	 * @param 	gMessage
	 * 			model backing the information about the displayed message
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded
	 * 
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	private void build(final GenerationMessage gMessage)
		throws FileReadingException, UnknownKeyException {
		makeSourceRow(gMessage) ;
		makeSeparator() ;			
		makeTargetRows(gMessage) ;
		makeSeparator() ;	
		makeMessageRow(gMessage) ;
		
		this.reloadLanguage() ;
	}// build(GenerationMessage)

	/**
	 * Updates the model backing the information about the displayed message.
	 * 
	 * @param	newMessage
	 * 			new content for the model
	 * 
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	@Override
	public void updateMessage(final Message newMessage) throws UnknownKeyException {
		this.message = newMessage ;
		GenerationMessage gMessage = (GenerationMessage) newMessage ;
		
		// Update progress icons
		int rowIndex = 0 ;
		for(JLabel progressIcon : this.progressIcons) {
			try {
				this.initProgressIcon(progressIcon, gMessage.getState(rowIndex)) ;
			} catch (KameleonException ke) {
				this.model.displayDebugInformation(ke) ;
			}// try
			++rowIndex ;
		}// for
		
		// Update message
		this.reloadLanguage() ;
		
		// Update generation date
		TitledBorder border = (TitledBorder) this.getBorder() ;
		border.setTitle(FileModel.DATE_FORMATTER.format(gMessage.getLastGeneration())) ;
	}// updateMessage(Message)

	/**
	 * Builds the source row of this instance. Displays the name and format
	 * of the source file for the matching generation process.
	 * 
	 * @param	gMessage
	 * 			model backing the information about the displayed message
	 */
	private void makeSourceRow(final GenerationMessage gMessage) {
		JLabel label ;
		GridBagConstraints constraints ;

		// -----
		this.convertingTitle = new JLabel() ;
		constraints = new GridBagConstraints() ;
		constraints.gridwidth = 2 ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
		constraints.insets = new Insets(2, 2, 2, 2) ;
		constraints.weightx = 0.0 ;
		constraints.weighty = 0.0 ;
		this.add(this.convertingTitle) ;
		this.gridbag.setConstraints(this.convertingTitle, constraints) ;
		
		// -----
		try {
			label = new JLabel(new ImageIcon(
					ImageUtility.getImageBytes(String.format(
							MINI_ICON, gMessage.getIdFormat())))) ;
			label.setToolTipText(this.model.getAnalyzer(
					gMessage.getIdFormat()).getFormatName()) ;
			constraints = new GridBagConstraints() ;
			constraints.gridwidth = 1 ;
			constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
			constraints.insets = new Insets(2, 2, 2, 2) ;
			constraints.weightx = 0.0 ;
			constraints.weighty = 0.0 ;
			this.add(label) ;
			this.gridbag.setConstraints(label, constraints) ;
		} catch(KameleonException ke) {
			this.model.displayDebugInformation(ke) ;
		}// try
		
		// -----
		label = new JLabel(gMessage.getPath()) ;
		label.setFont(this.TRUETYPE) ;
		constraints = new GridBagConstraints() ;
		constraints.gridwidth = GridBagConstraints.REMAINDER ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
		constraints.insets = new Insets(2, 2, 2, 2) ;
		constraints.weightx = 1.0 ;
		constraints.weighty = 0.0 ;
		this.add(label) ;
		this.gridbag.setConstraints(label, constraints) ;
	}// makeSourceRow(GenerationMessage)

	/**
	 * Builds the rows for every generated file. Displays the name, format
	 * and generation state for each target file.
	 * 
	 * @param	gMessage
	 * 			model backing the information about the displayed message
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void makeTargetRows(final GenerationMessage gMessage) 
			throws FileReadingException{
		String[] paths = gMessage.getTargetPath() ;
		String[] formatsIds = gMessage.getTargetFormatId() ;
		State[] states = gMessage.getStates() ;
		int nRows = paths.length ;
		this.toTitles = new JLabel[nRows] ;
		this.progressIcons = new JLabel[nRows] ;
		for(int row=0; row < nRows; ++row) {
			if ((paths[row] != null) && (formatsIds[row] != null)) {
				makeTargetRow(row, paths[row], formatsIds[row], states[row]) ;
			}// if
		}// for
	}// makeTargetRows(GenerationMessage)

	/**
	 * Utility function, simply calls
	 * <pre>makeRow(rowIndex, "To", path, formatId,
	 * GridBagConstraints.ABOVE_BASELINE_TRAILING, 
	 * this.model.getGenerator(formatId).getFormatName(), 
	 * state) ;
	 * </pre>
	 * (the "To" is translated into the correct language.)
	 * 
	 * @param 	rowIndex
	 * 			index of the generated row
	 * 
	 * @param 	path
	 * 			path of the target file
	 * 
	 * @param 	formatId
	 * 			identifier of the format of the target file
	 * 
	 * @param 	state
	 * 			current state of the generation process for the target file
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void makeTargetRow(int rowIndex, String path, String formatId, State state) 
			throws FileReadingException {
		makeRow(rowIndex, "To", path, formatId, //$NON-NLS-1$
				GridBagConstraints.ABOVE_BASELINE_TRAILING, 
				this.model.getGenerator(formatId).getFormatName(), 
				state) ;
	}// makeTargetRow(int, String, String, State)

	/**
	 * Builds a rowa for a generated file. Displays the name, format
	 * and generation state using the given values.
	 * 
	 * @param 	rowIndex
	 * 			index of the generated row
	 * 
	 * @param	title
	 * 			text displayed in the first column (title of the row)
	 * 
	 * @param 	path
	 * 			path of the target file
	 * 
	 * @param 	formatId
	 * 			identifier of the format of the target file
	 * 
	 * @param	firstCellAnchor
	 * 			anchor used by this row
	 * 
	 * @param	tooltip
	 * 			tool tip displayed when hovering the format icon
	 * 
	 * @param 	state
	 * 			current state of the generation process for the target file
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void makeRow(int rowIndex, String title, String path, String formatId,
			int firstCellAnchor, String tooltip, State state) 
					throws FileReadingException {
		JLabel label ;
		GridBagConstraints constraints ;

		// -----
		this.progressIcons[rowIndex] = this.makeProgressIcon(state) ;

		// -----
		label = new JLabel(title) ;
		constraints = new GridBagConstraints() ;
		constraints.gridwidth = 1 ;
		constraints.anchor = firstCellAnchor ;
		constraints.insets = new Insets(2, 2, 2, 2) ;
		constraints.weightx = 0.0 ;
		constraints.weighty = 0.0 ;
		this.add(label) ;
		this.gridbag.setConstraints(label, constraints) ;
		this.toTitles[rowIndex] = label ;

		// -----
		try {
			label = new JLabel(new ImageIcon(
					ImageUtility.getImageBytes(String.format(
							MINI_ICON, formatId)))) ;
			label.setToolTipText(tooltip) ;
			constraints = new GridBagConstraints() ;
			constraints.gridwidth = 1 ;
			constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
			constraints.insets = new Insets(2, 2, 2, 2) ;
			constraints.weightx = 0.0 ;
			constraints.weighty = 0.0 ;
			this.add(label) ;
			this.gridbag.setConstraints(label, constraints) ;
		} catch(KameleonException ke) {
			this.model.displayDebugInformation(ke) ;
		}// try

		// -----
		label = new JLabel(path) ;
		label.setFont(this.TRUETYPE) ;
		constraints = new GridBagConstraints() ;
		constraints.gridwidth = GridBagConstraints.REMAINDER ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
		constraints.insets = new Insets(2, 2, 2, 2) ;
		constraints.weightx = 1.0 ;
		constraints.weighty = 0.0 ;
		this.add(label) ;
		this.gridbag.setConstraints(label, constraints) ;
	}// makeRow(int, String, String, String, int, String, State)

	/**
	 * Builds a message resuming the global state of the generation process
	 * and a button to trigger a re-run of the generation process.
	 * 
	 * @param	gMessage
	 * 			model backing the information about the current generation process
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void makeMessageRow(final GenerationMessage gMessage) 
			throws FileReadingException {
		GridBagConstraints constraints ;

		makeReGenerateButton(gMessage) ;

		this.messageLabel = new JLabel() ;
		constraints = new GridBagConstraints() ;
		constraints.gridwidth = GridBagConstraints.REMAINDER ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
		constraints.weightx = 1.0 ;
		constraints.weighty = 0.0 ;
		this.add(this.messageLabel) ;
		this.gridbag.setConstraints(this.messageLabel, constraints) ;
	}// makeMessageRow(GenerationMessage)

	/**
	 * Builds the button used to trigger a re-run of the generation process.
	 * 
	 * @param 	gMessage
	 * 			model backing the information about the current generation process
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void makeReGenerateButton(final GenerationMessage gMessage)
			throws FileReadingException {
		GridBagConstraints constraints ;

		InputStream src = this.getClass().getResourceAsStream(REGENERATE_FILE) ;
		Icon refreshIcon = new ImageIcon(
				ImageUtility.getImageBytes(new BufferedInputStream(src))) ;
		
		final JButton button = new JButton(refreshIcon) ;
		button.setContentAreaFilled(false) ;
		button.setBorder(null) ;
		button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)) ;
		final Model fViewModel = this.model ;
		button.addActionListener(new ActionListener() {				
			@Override
			public void actionPerformed(ActionEvent e) {
				button.setEnabled(false) ;
				fViewModel.fastGeneration(gMessage) ;
			}// actionPerformed(ActionEvent)
		}) ;

		constraints = new GridBagConstraints() ;
		constraints.gridwidth = 2 ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE_LEADING ;
		constraints.weightx = 0.0 ;
		constraints.weighty = 0.0 ;
		this.add(button) ;
		this.gridbag.setConstraints(button, constraints) ;

		this.fastGeneration = button ;
	}// makeReGenerateButton(GenerationMessage)

	/**
	 * Builds an invisible separator row.
	 */
	private void makeSeparator() {
		JPanel separator = new JPanel() ;
		GridBagConstraints constraints = new GridBagConstraints() ;
		constraints.gridwidth = GridBagConstraints.REMAINDER ;
		constraints.fill = GridBagConstraints.BOTH ;
		this.add(separator) ;
		this.gridbag.setConstraints(separator, constraints) ;
	}// makeSeparator()

	/**
	 * Builds a progress icon for a given state.
	 * 
	 * @param 	state
	 * 			state used to determine the matching progress icon
	 * 
	 * @return	A {@code JLabel} with the matching progress icon
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private JLabel makeProgressIcon(State state) throws FileReadingException {
		JLabel label = null ;
		GridBagConstraints constraints ;

		label = new JLabel() ;
		this.initProgressIcon(label, state) ;

		constraints = new GridBagConstraints() ;
		constraints.gridwidth = 1 ;
		constraints.anchor = GridBagConstraints.ABOVE_BASELINE ;
		constraints.insets = new Insets(2, 2, 2, 2) ;
		constraints.weightx = 0.0 ;
		constraints.weighty = 0.0 ;
		this.add(label) ;
		this.gridbag.setConstraints(label, constraints) ;

		return label ;
	}// makeProgressIcon(State)

	/**
	 * Initializes the progress icon of the given label using the
	 * given state.
	 * 
	 * @param 	label
	 * 			label that will contain the progress icon
	 * 
	 * @param 	state
	 * 			state used to determine the matching progress icon
	 * 
	 * @throws 	FileReadingException
	 * 			If a format icon could not be loaded 
	 */
	private void initProgressIcon(JLabel label, State state) 
			throws FileReadingException {
		String icon = null ;
		
		switch(state) {
		case WAITING:
			icon = WAITING_FILE ;
			break ;
			
		case CONVERTING:
			icon = CONVERTING_FILE ;
			break ;

		case CONVERTED:
			icon = CONVERTED_FILE ;
			break ;

		case ERROR:
			icon = ERROR_FILE ;
		}// switch
		
		InputStream src = new BufferedInputStream(ViewGenerationMessage.class
				.getResourceAsStream(icon)) ;
		label.setIcon(new ImageIcon(
				ImageUtility.getImageBytes(src))) ;
		try {
			src.close() ;
		} catch(IOException ioe) {
			this.model.displayDebugInformation(
					new FileReadingException(ioe.getMessage())) ;
		}// try
	}// initProgressIcon(JLabel, State)

	/**
	 * Enables or disables the regenerate button.
	 */
	@Override
	public void update() {
		if (!this.fastGeneration.isEnabled() && this.model.generationFinished()) {
			this.fastGeneration.setEnabled(true) ;
		}// if
	}// update()

	/**
	 * Updates the text to match the currently selected language.
	 * 
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	@Override
	public void reloadLanguage() throws UnknownKeyException {
		SwitchLanguage sl = SwitchLanguage.getInstance() ;
		GenerationMessage gMessage = (GenerationMessage) this.message ;
		
		this.convertingTitle.setText(sl.getText(CONVERTING)) ;
		State[] states = gMessage.getStates() ;
		int nRows = states.length ;
		for(int row=0; row < nRows; ++row) {
			this.toTitles[row].setText(sl.getText(TO)) ;
			this.progressIcons[row].setToolTipText(
					sl.getText(getTextKey(states[row]))) ;
		}// for
		this.fastGeneration.setText(sl.getText(REGENERATE)) ;
		this.messageLabel.setText(this.getMessageText()) ;
	}// reloadLanguage()
	
	/**
	 * Returns the language key used to describe the given state.
	 * 
	 * @param 	state
	 * 			state whose language key is requested
	 * 
	 * @return	Language key for the given state
	 */
	private static String getTextKey(State state) {
		switch(state) {
		case WAITING:    return STATE_WAITING ;
		case CONVERTING: return STATE_CONVERTING ;
		case CONVERTED:  return STATE_CONVERTED ;
		default: /* case ERROR */
		}// switch
		return STATE_ERROR ;
	}// getTextKey(State)
	
	/**
	 * Returns a description for the current state of the generation process.
	 * 
	 * @return	Description for the current state of the generation process
	 * 
	 * @throws 	UnknownKeyException
	 * 			if an error occurred while updating the text of this view
	 */
	private String getMessageText() throws UnknownKeyException {
		SwitchLanguage sl = SwitchLanguage.getInstance() ;
		GenerationMessage gMessage = (GenerationMessage) this.message ;
		String textKey, filePath = null ;
		switch(gMessage.getState()) {
		case ANALYZING: {
			textKey = GENERATION_ANALYZING_START ;
			break ;
		}// case
		case GENERATING: {
			State[] states = gMessage.getStates() ;
			if (states[0].equals(State.WAITING)) {
				textKey = GENERATION_ANALYZING_SUCCESS ;
			} else {
				textKey = GENERATION_GENERATING ;
				int i = 0, nStates = states.length ;
				while((i < nStates) && (filePath == null)) {
					if (states[i].equals(State.CONVERTING)) {
						filePath = gMessage.getTargetPath()[i] ;
					}// if
					++i ;
				}// while
			}// if
			break ;
		}// case
		case PARTIAL_SUCESS: {
			textKey = GENERATION_PARTIAL_SUCCESS ;
			break ;
		}// case
		case COMPLETE_SUCESS: {
			textKey = GENERATION_SUCCESS ;
			break ;
		}// clase
		default: {/* ERROR */
			textKey = GENERATION_ERROR ;
		}// default
		}// switch
		
		return sl.getText(textKey, filePath) ;
	}// getMessageText()

}// class ViewGenerationMessage